enum Type {
  Strong,
  Week,
}

class Java {
  helloJava() {
    console.log("Hello Java");
  }
  // 比如说我们在这个两个类中分别加入一个属性
  java: any;
}

class JavaScript {
  helloJavaScript() {
    console.log("Hello JavaScript");
  }
  javaScript: any;
}

function getLanguage(type: Type) {
  let lang: Java | JavaScript =
    type === Type.Strong ? new Java() : new JavaScript();
  // 我们需要先判断一下这个实例是不是有 helloJava 方法
  // 因为 lang 是两个类的联合类型，那这个时候呢 TS 是没法去判断它到底是那种类型的，我们需要加上一个类型断言 `as`
  if ((lang as Java).helloJava) {
    (lang as Java).helloJava(); // 如果有呢我们就执行下这个方法
  } else {
    // 否则呢就执行一个 helloJavaScript 方法
    (lang as JavaScript).helloJavaScript();
  }
  return lang;
}
// 那么因为我们不知道程序在运行的时候到底会传入什么样的参数，所以呢就必须要在每一次都加上类型断言，那么这显然不是一个理想的方案，代码呢可读性很差
getLanguage(Type.Strong);

// 类型保护

// 1、) instanceof
function getLanguage1(type: Type) {
  let lang: Java | JavaScript =
    type === Type.Strong ? new Java() : new JavaScript();
  // 判断一下如果 lang 是属于 Java 实例的话
  if (lang instanceof Java) {
    // 那么在这个区块中呢TypeScript就可以保证 lang 一定是 Java 的实例
    lang.helloJava(); // 相应的方法也会直接提示给我们
  } else {
    // 那么在另一个区块中呢TypeScript就会判断这个 lang 对象一定是 JavaScript 实例
    lang.helloJavaScript();
  }
  return lang;
}
// 这样代码就比上面简洁了很多
getLanguage1(Type.Strong);

// 2、) in
function getLanguage2(type: Type) {
  let lang: Java | JavaScript =
    type === Type.Strong ? new Java() : new JavaScript();

  // 然后呢我们就可以通过这两个属性`java: any;和 javaScript: any;`来创建类型保护区块了
  // 比如我们先判断，'java' 属性是不是在这个对象中
  // 这样呢两个类型保护区块链也会被创建了
  if ("java" in lang) {
    // 在第一个区块中呢，TypeScript 会判断这个对象呢一定是 Java 类的实例
    lang.helloJava();
  } else {
    // 在第二个区块中呢这一定是 JavaScript 的实例
    lang.helloJavaScript();
  }
  return lang;
}
getLanguage2(Type.Strong);

// 3、）typeof

// 我们给这个函数增加一个参数`x`它的类型呢是一个联合类型
function getLanguage3(type: Type, x: string | number) {
  let lang: Java | JavaScript =
    type === Type.Strong ? new Java() : new JavaScript();

  // 那么我们就可以用 typeof 来判断 x 的类型
  if (typeof x === "string") {
    // 那么在这个区块中 x 的类型就一定是 string 类型，它可以拥有 string 的一些属性
    console.log(x.length); // 比如说 length 属性
  } else {
    // 那么在另一个区块中呢，它一定就是 number 类型，同样也会有一些 number 类型的方法
    console.log(x.toFixed(2));
  }
  return lang;
}
// 那么好了，这是第三种类型保护的方法
getLanguage3(Type.Strong, 1);

// 4、）创建一个类型保护函数来判断对象的类型

// 这个函数需要我们自定义，比如说这个名字叫 isJava ，那么它的参数呢就是一个联合类型，这个函数的返回值呢是一种特殊的返回值，
// 它的格式是这样的：首先呢是参数`lang`加上一个`is`在加上一个类型`java`，这种返回值呢叫做类型谓词
function isJava(lang: Java | JavaScript): lang is Java {
  // 那么在返回的时候呢我们要判断一下这个对象是不是有 helloJava 方法
  return (lang as Java).helloJava !== undefined; // 那么同样这里我们要用一下类型断言，那么好了这个类型保护函数呢我们就书写完了
}

// 那么下面呢我们可以用这个函数来判断
function getLanguage4(type: Type) {
  let lang: Java | JavaScript =
    type === Type.Strong ? new Java() : new JavaScript();

  if(isJava(lang)){
    // 那么同样也创建了一个类型保护区块
    console.log(lang.helloJava());
  }else{
    console.log(lang.helloJavaScript());
  }
  return lang;
}
